package com.gqr.threadsafe;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Copyright (C), 2020-2024,gqr
 * FileName:MultiThreadsError
 * author:guqingrong
 * Date:2024-04-26 22:04
 * Description:第一种:运行结果出错
 * 演示计数不准确(减少),找出具体出错的位置
 * History:
 * <author>    <time>    <version>    <desc>
 * 作者姓名     修改时间     版本号        描述
 */
public class MultiThreadsError implements Runnable {
    static MultiThreadsError instance = new MultiThreadsError();
    int index = 0;
    final boolean[] marked = new boolean[10000000];

    static AtomicInteger realIndex = new AtomicInteger();
    static AtomicInteger wrongCount = new AtomicInteger();

    /**
     * 让线程根据我们的需要在某一个地方等待 直到所等待的线程都就绪了 再一起执行
     */
    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2); // 2 代表等待2个线程
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);


    @Override
    public void run() {
//        while (index<10000){
//            index++;
//        }
//        synchronized (instance){
        marked[0]=true;
        for (int i = 0; i < 1000000; i++) {
            try {
                // 两个线程都执行过await,代表所等待的线程都到齐了 就会放行
                cyclicBarrier2.reset();
                cyclicBarrier1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            index++;
            try {
                cyclicBarrier1.reset();
                cyclicBarrier2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            realIndex.incrementAndGet();
            /**
             * 下面的marked判断和赋值其实也是有线程冲突的
             * 改造一下 由synchronized保护一下 wrongCount反而差距更大了 更离谱了
             * 原因: 假设2个线程执行完前面的2行计算 (index=0 发生冲突了两个线程的index都是1了) 准备进入下面的同步代码块 拿锁 假设线程1拿到了锁
             * 线程1 执行了同步代码块内的内容 marked[1]=true 执行完同步代码块 线程1又去执行index++(index=1) 此时index=2 切换到线程2
             * 线程2 进入同步代码块 marked[index=2]而不是1 线程2其实想检查的是index=1的情况
             * 其实就是synchronized执行完后 2个线程又要重新竞争锁 有可能是一直是线程1一直连续拿到锁 也有可能是线程2
             * index从最开始的0 执行index++ 两个线程都当作index=1 如果线程1 先抢到锁 执行完同步代码块后 再次执行index++ index就变成了2
             * 当切换到线程2执行if检查时 原本想检查的是index=1的情况 但是index却变成了2
             * ==========
             *
             *
             */
//            if(marked[index]){
//                System.out.println("发生了错误"+index);
//                wrongCount.incrementAndGet();
//            }
//            marked[index] = true;
            synchronized (instance) {
//                System.out.println(Thread.currentThread().getName()+"的index:"+index);
                /**
                 * 判断条件增加 && marked[index-1]
                 * 前一位是false 是正常情况
                 * 两道栅栏 外加synchronized 保证可见性
                 * 线程1 会把marked[2] 赋值为true
                 * 线程2进来 也只能看到marked[2] 而index=1或者index=奇数位是没有线程修改的(这种情况是正常情况 没有发生线程错误)
                 * 只不过由于可见性的保证没有对于index=1或index=奇数位进行标记为true而已
                 * 只有当前和前一位都是true 才认定是发生了错误
                 * 两个线程在循环里执行index++ 每次循环都有栅栏保护都是加2才是正确的,
                 * 当线程发生争抢错误了 index++不是2 比如说是1 index=0标记为true,index=1也标记为true[正常情况index=1是false]
                 * 这样就满足发生错误判断条件
                 */
                if (marked[index] && marked[index-1]) {
                    System.out.println("发生了错误" + index);
                    wrongCount.incrementAndGet();
                }
                marked[index] = true;
            }


        }
//        }

    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(instance,"线程1");
        Thread thread2 = new Thread(instance,"线程2");
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("表面上的结果" + instance.index);
        System.out.println("真正运行的次数" + realIndex.get());
        System.out.println("错误的次数" + wrongCount.get());
    }

}
